package com.boot.interceptor;

import cn.hutool.core.util.StrUtil;
import com.boot.annotation.RateLimit;
import com.boot.service.IpBlacklistService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;

@Component
public class RateLimitInterceptor implements HandlerInterceptor {

    private static final Logger log = LoggerFactory.getLogger(RateLimitInterceptor.class);

    // 外部注入黑名单服务
    private final IpBlacklistService ipBlacklistService;

    public RateLimitInterceptor(IpBlacklistService ipBlacklistService) {
        this.ipBlacklistService = ipBlacklistService;
        startCleanupTask(); // 启动后台清理任务
    }

    // 每个 IP 的限流计数器 key: ip_window -> count
    private final Map<String, Integer> requestCounts = new ConcurrentHashMap<>();
    private final Map<String, Long> windowStarts = new ConcurrentHashMap<>();
    // 记录每个 key 最后一次被访问的时间（用于过期判断）
    private final Map<String, Long> lastAccessTime = new ConcurrentHashMap<>();

    // 清理周期：每5分钟扫描一次
    private static final long CLEAN_UP_INTERVAL_MILLIS = 5 * 60 * 1000;
    // 数据最大空闲时间：比如超过最长窗口 + 缓冲时间即视为过期（例如 2 小时）
    private static final long EXPIRE_AFTER_IDLE_MILLIS = 7200 * 1000; // 2小时

    // 同一 IP 允许被限流的次数（比如最多容忍3次/小时内）
    private static final int MAX_RATE_LIMIT_HITS = 3;
    // 统计周期：1小时（用于 abuse 检测）
    private static final long ALERT_WINDOW_SECONDS = 3600;

    private static final String HEADER_X_FORWARDED_FOR = "X-Forwarded-For";
    private static final String HEADER_X_REAL_IP = "X-Real-IP";

    // 可选：限制总条目数，防止极端情况下的膨胀
    private static final int MAX_ENTRIES = 10_000;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        RateLimit rateLimit = handlerMethod.getMethodAnnotation(RateLimit.class);
        if (rateLimit == null) {
            return true;
        }

        String clientIp = getClientIp(request);
        int limit = rateLimit.limit();
        int windowSec = rateLimit.windowSec();

        // === 第一步：检查是否已在黑名单中 ===
        if (ipBlacklistService.isBlacklisted(clientIp)) {
            sendForbidden(response, "您的IP已被封禁，请" + ipBlacklistService.getRemainingTimeSeconds(clientIp) + "秒后重试");
            log.warn("黑名单IP {} 尝试访问受保护接口", clientIp);
            return false;
        }

        // === 第二步：执行常规限流逻辑 ===
        // ✅ 使用 rateLimit.name() 替代 URI，避免因路径参数导致 key 泛滥
        // 如未指定 name，则 fallback 到 method + uri（但仍建议显式命名）
        String uri = request.getRequestURI();
        String method = request.getMethod();
        String resourceName = method + ":" + uri;

        String key = clientIp + "_" + resourceName + "_" + windowSec;
        long nowSeconds = Instant.now().getEpochSecond();
        long nowMillis = System.currentTimeMillis();

        // 更新最后访问时间
        lastAccessTime.put(key, nowMillis);

        // 检查窗口是否过期
        Long windowStart = windowStarts.get(key);
        if (windowStart == null || nowSeconds - windowStart > windowSec) {
            // 窗口过期，重置计数
            windowStarts.put(key, nowSeconds);
            requestCounts.put(key, 1);
            return true;
        }

        // 获取当前请求数
        int currentCount = requestCounts.getOrDefault(key, 0);
        if (currentCount >= limit) {
            // 触发限流 → 记录一次“违规”
            handleRateLimitExceeded(request, response, clientIp, limit, windowSec);
            return false;
        }

        // 增加计数
        requestCounts.put(key, currentCount + 1);
        return true;
    }

    /**
     * 处理超出限流的情况：记录日志，并判断是否需要拉入黑名单
     */
    private void handleRateLimitExceeded(HttpServletRequest request, HttpServletResponse response,
                                         String clientIp, int limit, int windowSec) throws IOException {
        log.warn("IP {} 在接口 {} 请求过于频繁，已被限流 [limit={}/{}s]", clientIp, request.getRequestURI(), limit, windowSec);

        // 记录本次限流事件
        recordAbuse(clientIp);

        sendTooManyRequests(response, "请求过于频繁，请稍后再试");
    }

    /**
     * 记录滥用行为：如果短时间内多次被限流，则拉入黑名单
     */
    private void recordAbuse(String clientIp) {
        String alertKey = clientIp + "_alert";
        long nowMillis = System.currentTimeMillis();

        // 更新最后访问时间
        lastAccessTime.put(alertKey, nowMillis);

        Long windowStart = windowStarts.getOrDefault(alertKey, nowMillis);
        int hitCount = requestCounts.getOrDefault(alertKey, 0);

        if (nowMillis - windowStart > ALERT_WINDOW_SECONDS * 1000) {
            // 超出统计窗口，重置
            windowStarts.put(alertKey, nowMillis);
            requestCounts.put(alertKey, 1);
        } else {
            int newCount = hitCount + 1;
            requestCounts.put(alertKey, newCount);
            if (newCount >= MAX_RATE_LIMIT_HITS) {
                // 达到阈值，加入黑名单
                ipBlacklistService.blacklistIp(clientIp);
                log.warn("IP {} 因频繁触发限流，已被加入黑名单", clientIp);
            }
        }
    }

    /**
     * 获取客户端真实IP地址
     */
    private String getClientIp(HttpServletRequest request) {
        String xff = request.getHeader(HEADER_X_FORWARDED_FOR);
        if (StrUtil.isNotBlank(xff)) {
            return xff.split(",")[0].trim();
        }
        String xri = request.getHeader(HEADER_X_REAL_IP);
        if (StrUtil.isNotBlank(xri)) {
            return xri.trim();
        }
        return request.getRemoteAddr();
    }

    /**
     * 发送 429 响应
     */
    private void sendTooManyRequests(HttpServletResponse response, String message) throws IOException {
        response.setStatus(429);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write("{\"code\":429,\"message\":\"" + message + "\",\"data\":null}");
    }

    /**
     * 发送 403 响应
     */
    private void sendForbidden(HttpServletResponse response, String message) throws IOException {
        response.setStatus(403); // Forbidden
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write("{\"code\":403,\"message\":\"" + message + "\",\"data\":null}");
    }

    /**
     * 启动后台清理线程：定期清除长时间未使用的限流数据
     */
    private void startCleanupTask() {
        ThreadFactory factory = r -> {
            Thread t = new Thread(r, "rate-limit-cleanup-thread");
            t.setDaemon(true); // 守护线程，随JVM退出自动结束
            return t;
        };

        Thread cleanupThread = factory.newThread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Thread.sleep(CLEAN_UP_INTERVAL_MILLIS);
                    long now = System.currentTimeMillis();
                    int removed = 0;

                    // 遍历 lastAccessTime，找出过期的 key 并清理
                    for (Map.Entry<String, Long> entry : lastAccessTime.entrySet()) {
                        if (now - entry.getValue() > EXPIRE_AFTER_IDLE_MILLIS) {
                            String key = entry.getKey();
                            // 移除三张表中的记录
                            lastAccessTime.remove(key);
                            requestCounts.remove(key);
                            windowStarts.remove(key);
                            removed++;
                        }
                    }

                    // 可选：当总量过大时强制触发一次清理
                    int totalSize = requestCounts.size();
                    if (totalSize >= MAX_ENTRIES) {
                        log.warn("限流数据量已达 {}，接近上限 {}，建议检查或扩容", totalSize, MAX_ENTRIES);
                    }

                    if (removed > 0) {
                        log.info("自动清理过期限流数据 {} 条，当前剩余 {} 条", removed, requestCounts.size());
                    }

                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 恢复中断状态
                    break;
                } catch (Exception e) {
                    log.error("清理限流数据时发生异常", e);
                }
            }
        });

        cleanupThread.start();
    }
}
